package com.matrix.order.application.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.matrix.api.im.dto.OrderCardDTO;
import com.matrix.api.marketing.dto.RefundCalculationRequestDTO;
import com.matrix.api.marketing.vo.RefundCalculationResultVO;
import com.matrix.api.merchant.vo.ShopDetailVO;
import com.matrix.api.order.OrderApi;
import com.matrix.api.order.dto.AddDishDTO;
import com.matrix.api.order.dto.CancelDishDTO;
import com.matrix.api.order.dto.CreateOrderDTO;
import com.matrix.api.order.vo.*;
import com.matrix.common.constant.RocketMQConstants;
import com.matrix.common.domain.PageResult;
import com.matrix.common.enums.OrderStatusEnum;
import com.matrix.common.enums.UserTypeEnum;
import com.matrix.common.exception.BusinessException;
import com.matrix.common.exception.OrderNotFoundException;
import com.matrix.order.application.OrderAppService;
import com.matrix.order.converter.OrderConverter;
import com.matrix.order.domain.factory.OrderFactory;
import com.matrix.order.domain.model.Order;
import com.matrix.order.domain.model.OrderItem;
import com.matrix.order.domain.model.OrderPayment;
import com.matrix.order.domain.repository.OrderRepository;
import com.matrix.order.domain.service.OrderDomainService;
import com.matrix.order.infrastructure.gateway.acl.ImAcl;
import com.matrix.order.infrastructure.gateway.acl.MarketingAcl;
import com.matrix.order.infrastructure.gateway.acl.MerchantAcl;
import com.matrix.order.infrastructure.gateway.acl.PaymentAcl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 有点甜
 * @since 2025/8/2
 */
@Slf4j
@Service
@DubboService
@RequiredArgsConstructor
public class OrderAppServiceImpl implements OrderAppService, OrderApi {

    private final OrderRepository orderRepository;
    private final OrderFactory orderFactory;
    private final OrderDomainService orderDomainService;
    private final MerchantAcl merchantAcl;
    private final PaymentAcl paymentAcl;
    private final ImAcl imAcl;
    private final MarketingAcl marketingAcl;
    private final RedissonClient redissonClient;
    private final RocketMQTemplate rocketMQTemplate;

    @Override
    public AsyncOrderResultVO createOrderAsync(Long userId, CreateOrderDTO orderDTO) {
        String orderNo = String.valueOf(IdUtil.getSnowflake().nextId());
        Map<String, Object> messagePayload = new ConcurrentHashMap<>();
        messagePayload.put("userId", userId);
        messagePayload.put("orderNo", orderNo);
        messagePayload.put("createOrderDTO", orderDTO);
        try {
            TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(
                    RocketMQConstants.ORDER_CREATION_TOPIC,
                    MessageBuilder.withPayload(messagePayload).build(),
                    Map.of("userId", userId, "orderNo", orderNo)
            );
            if (sendResult.getSendStatus() == SendStatus.SEND_OK) {
                log.info("订单 {} 的事务半消息发送成功", orderNo);
                return new AsyncOrderResultVO(orderNo, "PROCESSING");
            } else {
                log.error("订单 {} 的事务半消息发送失败, status: {}", orderNo, sendResult.getSendStatus());
                throw new BusinessException("下单失败,请稍后重试");
            }
        } catch (Exception e) {
            log.error("发送订单创建事务消息时出现异常, userId: {}", userId, e);
            throw new BusinessException("系统繁忙,下单失败");
        }
    }

    @Override
    public OrderCardDTO getOrderCardInfo(Long orderId, Long requesterId) {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new OrderNotFoundException("订单不存在"));

        if (!order.getUserId().equals(requesterId) && !order.getShopOwnerId().equals(requesterId)) {
            throw new BusinessException("您没有权限查看此订单");
        }

        ShopDetailVO shopDetails = merchantAcl.getShopDetailsById(order.getShopId());

        List<String> thumbnails = order.getItems().stream()
                .map(OrderItem::getProductImageUrl)
                .filter(StringUtils::hasText)
                .limit(4)
                .collect(Collectors.toList());

        return OrderCardDTO.builder()
                .orderId(order.getId())
                .orderNo(order.getOrderNo())
                .shopLogoUrl(shopDetails.getLogoUrl())
                .shopName(shopDetails.getShopName())
                .statusText(order.getStatus().getDescription())
                .totalAmount(order.getActualAmount())
                .totalItemsCount(order.getItems().size())
                .productThumbnails(thumbnails)
                .build();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Order processOrderCreation(Long userId, CreateOrderDTO orderDTO, String preAssignedOrderNo) {
        String areaSeat = orderDTO.getAreaSeat();
        String lockKey = String.format("lock:create_order:%s:%s", orderDTO.getShopId(), areaSeat);
        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (!lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                throw new BusinessException("系统繁忙,请稍后再试");
            }
            merchantAcl.checkShopStatus(orderDTO.getShopId());
            merchantAcl.checkTableAvailability(orderDTO.getShopId(), orderDTO.getAreaSeat());

            BigDecimal platformDiscount = marketingAcl.useCoupon(userId, orderDTO.getUserCouponId(), -1L);
            BigDecimal merchantDiscount = marketingAcl.useCoupon(userId, orderDTO.getMerchantCouponId(), -1L);

            Order order = orderFactory.create(userId, orderDTO, preAssignedOrderNo, platformDiscount, merchantDiscount);
            orderRepository.save(order);

            if (order.getUserCouponId() != null && order.getUserCouponId() > 0) {
                marketingAcl.updateCouponOrderId(order.getUserCouponId(), order.getId());
            }
            if (order.getMerchantCouponId() != null && order.getMerchantCouponId() > 0) {
                marketingAcl.updateCouponOrderId(order.getMerchantCouponId(), order.getId());
            }

            return order;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            marketingAcl.releaseCoupon(orderDTO.getUserCouponId());
            marketingAcl.releaseCoupon(orderDTO.getMerchantCouponId());
            throw new BusinessException("系统中断,订单处理失败" + e);
        } catch (Exception e) {
            log.error("异步处理订单创建失败, userId: {}, aseat: {}", userId, areaSeat, e);
            marketingAcl.releaseCoupon(orderDTO.getUserCouponId());
            marketingAcl.releaseCoupon(orderDTO.getMerchantCouponId());
            throw e;
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handlePaymentSuccess(String paymentNo, Integer totalFee) {
        String lockKey = "lock:payment_notify:" + paymentNo;
        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (!lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                log.warn("支付回调：获取支付单 {} 的锁失败,可能正在被处理", paymentNo);
                return;
            }

            Order order = orderRepository.findByPaymentNo(paymentNo);
            if (order == null) {
                log.warn("支付回调:未找到支付单号为 {} 的订单", paymentNo);
                return;
            }

            BigDecimal paidAmount = BigDecimal.valueOf(totalFee).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
            order.paymentSuccess(paidAmount);

            orderRepository.save(order);
            imAcl.sendNewOrderNotification(order.getShopOwnerId(), order.getAreaSeat(), order.getId());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("支付回调处理被中断, paymentNo: {}", paymentNo, e);
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void acceptOrder(Long merchantUserId, Long orderId) {
        Long shopId = merchantAcl.getShopIdByOwnerId(merchantUserId);
        Order order = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new);
        order.accept(shopId);
        orderRepository.save(order);

        imAcl.sendOrderAcceptedNotification(order.getUserId(), order.getOrderNo(), order.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectOrder(Long merchantUserId, Long orderId, String reason) {
        Long shopId = merchantAcl.getShopIdByOwnerId(merchantUserId);
        Order order = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new);
        order.reject(shopId, reason, paymentAcl);
        orderRepository.save(order);
        imAcl.sendOrderRejectedNotification(order.getUserId(), order.getOrderNo(), reason, order.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void finishMeal(Long userId, Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new);
        order.finishMeal(userId);
        orderRepository.save(order);
        merchantAcl.markTableForCleanUp(order.getShopId(), order.getAreaSeat());
        if (order.getActualAmount().compareTo(BigDecimal.ZERO) > 0) {
            marketingAcl.addPointsForOrder(userId, order.getActualAmount(), order.getOrderNo());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AddDishResultVO addDish(Long userId, Long orderId, AddDishDTO addDishDTO) {
        Order order = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new);
        OrderPayment newPayment = orderDomainService.addDishesToOrder(order, userId, addDishDTO.getItems());
        orderRepository.save(order);
        String qrCodeUrl = paymentAcl.requestPaymentForAdditionalDish(newPayment);
        AddDishResultVO vo = new AddDishResultVO();
        vo.setPaymentNo(newPayment.getPaymentNo());
        vo.setQrCodeUrl(qrCodeUrl);
        vo.setTotalAmount(newPayment.getAmount());
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void requestCancelDish(Long userId, Long orderId, CancelDishDTO cancelDishDTO) {
        Order order = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new);
        List<OrderItem> canceledItems = order.requestCancelDishes(userId, cancelDishDTO.getOrderItemIds());
        orderRepository.save(order);
        imAcl.sendCancelDishRequestNotification(order.getShopOwnerId(), order.getAreaSeat(), canceledItems, cancelDishDTO.getReason(), order.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approveCancelDish(Long merchantUserId, Long orderItemId) {
        Order order = orderRepository.findByOrderItemId(orderItemId)
                .orElseThrow(() -> new BusinessException("订单项不存在"));
        Long shopId = merchantAcl.getShopIdByOwnerId(merchantUserId);

        OrderItem itemToCancel = order.findItemById(orderItemId);
        if(itemToCancel.getStatus() != 1) {
            log.warn("Order item {} is not in 'PENDING_CANCELLATION' state.", orderItemId);
            return;
        }

        order.approveCancelDish(shopId, orderItemId);

        RefundCalculationRequestDTO request = RefundCalculationRequestDTO.builder()
                .userId(order.getUserId())
                .orderId(order.getId())
                .platformCouponId(order.getUserCouponId())
                .merchantCouponId(order.getMerchantCouponId())
                .platformCouponDiscount(marketingAcl.getCouponDiscount(order.getUserCouponId()))
                .merchantCouponDiscount(marketingAcl.getCouponDiscount(order.getMerchantCouponId()))
                .canceledItemPrice(itemToCancel.getPrice().multiply(new BigDecimal(itemToCancel.getQuantity())))
                .build();

        RefundCalculationResultVO result = marketingAcl.processCouponRefund(request);
        log.info("Refund calculation result for order {}: {}", order.getId(), result.getProcessingLog());

        BigDecimal cashRefundAmount = result.getCashRefundAmount();
        BigDecimal originalActualAmount = order.getActualAmount();
        BigDecimal newActualAmount = originalActualAmount.subtract(cashRefundAmount);

        order.setActualAmount(newActualAmount);
        order.setUpdateTime(LocalDateTime.now());
        orderRepository.save(order);

        if (cashRefundAmount.compareTo(BigDecimal.ZERO) > 0) {
            paymentAcl.requestRefund(order, cashRefundAmount, "用户退菜");
        }

        imAcl.sendCancelDishApprovedNotification(order.getUserId(), itemToCancel.getProductName(), cashRefundAmount, order.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectCancelDish(Long merchantUserId, Long orderItemId) {
        Order order = orderRepository.findByOrderItemId(orderItemId).orElseThrow(() -> new BusinessException("订单项不存在"));
        Long shopId = merchantAcl.getShopIdByOwnerId(merchantUserId);
        order.rejectCancelDish(shopId, orderItemId);
        orderRepository.save(order);

        imAcl.sendCancelDishRejectedNotification(order.getUserId(), order.findItemById(orderItemId).getProductName(), order.getId());
    }

    @Override
    public void callWaiter(Long userId, Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new);
        if (!order.getUserId().equals(userId)) {
            throw new BusinessException("无权操作");
        }
        imAcl.sendWaiterCallNotification(order.getShopOwnerId(), order.getAreaSeat(), order.getId());
    }

    @Override
    public PageResult<OrderBriefVO> getOrderListForMerchant(Long userId, int pageNum, int pageSize, Integer status) {
        Long shopId = merchantAcl.getShopIdByOwnerId(userId);
        Page<Order> page = orderRepository.findPageByShopId(shopId, status, pageNum, pageSize);
        List<OrderBriefVO> voList = OrderConverter.INSTANCE.toOrderBriefVOList(page.getRecords());
        return new PageResult<>(voList, page.getTotal(), page.getPages());
    }

    @Override
    public OrderDetailVO getOrderDetail(Long orderId, Long userId, UserTypeEnum userType) {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new OrderNotFoundException("订单不存在"));

        if (userType == UserTypeEnum.USER) {
            if (!order.getUserId().equals(userId)) {
                throw new BusinessException("无权查看此订单");
            }
        } else if (userType == UserTypeEnum.MERCHANT) {
            if (!order.getShopOwnerId().equals(userId)) {
                throw new BusinessException("无权查看此订单");
            }
        } else {
            throw new BusinessException("无权操作");
        }

        OrderDetailVO vo = OrderConverter.INSTANCE.toOrderDetailVO(order);

        ShopDetailVO shopDetails = merchantAcl.getShopDetailsById(order.getShopId());
        if (shopDetails != null) {
            vo.setShopName(shopDetails.getShopName());
            vo.setShopLogoUrl(shopDetails.getLogoUrl());
        }

        return vo;
    }

    @Override
    @Transactional
    public void finishProduction(Long merchantUserId, Long orderId) {
        Long shopId = merchantAcl.getShopIdByOwnerId(merchantUserId);
        Order order = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new);
        order.finishProduction(shopId);
        orderRepository.save(order);
        imAcl.sendProductionCompletedNotification(order.getUserId(), order.getOrderNo(), order.getId());
    }

    @Override
    @Transactional
    public void startServing(Long merchantUserId, Long orderId) {
        Long shopId = merchantAcl.getShopIdByOwnerId(merchantUserId);
        Order order = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new);
        order.startServing(shopId);
        orderRepository.save(order);
    }

    @Override
    @Transactional
    public void finishServing(Long merchantUserId, Long orderId) {
        Long shopId = merchantAcl.getShopIdByOwnerId(merchantUserId);
        Order order = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new);
        order.finishServing(shopId);
        orderRepository.save(order);
        imAcl.sendServingCompletedNotification(order.getUserId(), order.getOrderNo(), order.getId());
    }

    @Override
    public OrderForReviewVO checkOrderReviewable(Long orderId, Long userId) {
        Order order = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new);
        if (!order.getUserId().equals(userId)) {
            throw new BusinessException("无权操作此订单");
        }
        if (!order.getStatus().canReview()) {
            throw new BusinessException("订单当前状态不可评价");
        }
        List<OrderItemForReviewVO> itemVOs = order.getItems().stream().map(item -> {
            OrderItemForReviewVO itemVO = new OrderItemForReviewVO();
            itemVO.setOrderItemId(item.getId());
            itemVO.setProductId(item.getProductId());
            return itemVO;
        }).collect(Collectors.toList());
        OrderForReviewVO vo = new OrderForReviewVO();
        vo.setShopId(order.getShopId());
        vo.setItems(itemVOs);
        return vo;
    }

    @Override
    @Transactional
    public void markOrderAsReviewed(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new);
        order.markAsReviewed();
        orderRepository.save(order);
    }

    @Override
    public Map<Long, Integer> getMonthlySalesByShopIds(List<Long> shopIds) {
        if (shopIds == null || shopIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return orderRepository.getMonthlySalesByShopIds(shopIds);
    }

    @Override
    @Transactional
    public void checkAndCloseTimeoutOrder(String orderNo) {
        orderRepository.findByOrderNo(orderNo).ifPresent(order -> {
            if (order.getStatus() == OrderStatusEnum.PENDING_PAYMENT) {
                log.info("订单 {} 支付超时,正在关闭...", orderNo);
                order.timeoutClose(paymentAcl);
                orderRepository.save(order);
            }
        });
    }

    @Override
    public Map<Long, Integer> getProductMonthlySales() {
        return orderRepository.getProductMonthlySales();
    }

    @Override
    public PageResult<OrderBriefVO> getUserOrders(Long userId, int pageNum, int pageSize, String statusGroup) {
        List<OrderStatusEnum> statusEnums = convertStatusGroupToEnumList(statusGroup);
        Page<Order> page = orderRepository.findPageByUserId(userId, statusEnums, pageNum, pageSize);
        List<OrderBriefVO> voList = OrderConverter.INSTANCE.toOrderBriefVOList(page.getRecords());
        return new PageResult<>(voList, page.getTotal(), page.getPages());
    }

    private List<OrderStatusEnum> convertStatusGroupToEnumList(String statusGroup) {
        if (!StringUtils.hasText(statusGroup)) {
            return Collections.emptyList();
        }
        return switch (statusGroup.toUpperCase()) {
            case "IN_PROGRESS" -> List.of(OrderStatusEnum.PENDING_ACCEPTANCE, OrderStatusEnum.IN_PROGRESS, OrderStatusEnum.WAITING_FOR_SERVING, OrderStatusEnum.SERVING, OrderStatusEnum.SERVING_COMPLETED);
            case "REVIEW" -> List.of(OrderStatusEnum.PENDING_REVIEW);
            case "REFUND" -> List.of(OrderStatusEnum.CANCELLED);
            case "COMPLETED" -> List.of(OrderStatusEnum.PENDING_REVIEW, OrderStatusEnum.COMPLETED);
            default -> Collections.emptyList();
        };
    }
}